' c/c++,h plugin for maxedit host

Strict

Import "../maxtk/maxtk.bmx"
'Import axe.maxedit

Global CKeywords$[]=[..
"#include","#define","#ifdef","#ifndef","#else","#endif","#pragma","#if","#undef"]

Global CExtensions$[]=["c","m","h"]

Type TCOptions Extends TOptionsPage

	Field	owner:TCPlugin
	Field	cpppath:TPathButton
	Field	syntaxhighlight:TButton
	Field	autoindent:TButton
	Field	tabsize:TNumberButton
	Field	editcolor:TColorButton
	Field	editfont:TFontButton
'	Field	editstyle:TTextStyle
	Field	styles:TTextStyle[4]
		
	Method Dirty(cause:TDataNode)
'		owner.SetBMXPath blitzmaxpath.ToString()
	End Method
	
	Method CreateCOptions:TCOptions( plugin:TCPlugin )
		Local	cppurl$
		
		Local name$=plugin.name

		SetName name+" Options"
		owner=plugin		
		plugin.editor.hostoptions.AddPage Self
		
		cppurl=""
		cpppath=AddPathButton(name+" Path",cppurl)
		syntaxhighlight=AddButton("Syntax Highlight",2)
		autoindent=AddButton("Automatic Indent",2)
		tabsize=AddNumberButton("Tab Size",4)
		editcolor=AddColorButton("Background",-1)
		editfont=AddFontButton("Font","Courier",12,0)
'		editstyle=AddTextStyle(Self,"Style")
		styles=New TTextStyle[4]
		styles[0]=AddTextStyle(Self,"Body")
		styles[1]=AddTextStyle(Self,"Comments")
		styles[2]=AddTextStyle(Self,"Constants")
		styles[3]=AddTextStyle(Self,"Keywords")
		Layout
		Return Self
	End Method

End Type


Type TCPlugin Extends TPlugin

	Field	name$
	Field	keywords$[]
	Field	extensions$[]

	Field	editor:TEditor
	Field	options:TCOptions
	Field	tokens:TQuickHelp
	Field	nomods
	Field	editmenu:TGadget
	
	Method New()
		name="C"
		keywords=CKeywords
		extensions=CExtensions
	End Method

	Method Open:Object(path$)
		Return TCCode.Open(Self,path)
	End Method

	Method Debug$(err$)
		Local f,p,q,r
		Local cerr$,file$,line
		cerr=err.Replace("~n   ","")
		While True		
			p=cerr.find(".cpp:",f)
			q=cerr.find(".h:",f)
			r=cerr.find(".c:",f)
			If p<0 And q<0 And r<0 Return err			
			If r>-1 And (r<q Or q=-1) q=r
			If (q>-1 And (q<p Or p=-1)) p=q+2 Else p=p+4			
			q=cerr.findlast("~n",cerr.length-p)+1			
			r=cerr.find("~n",p+1)
			If r=-1 r=cerr.length
			file=cerr[q..p]
			line=Int(cerr[p+1..r])
			If line
				editor.DebugSource file,line,1
				Notify cerr[..r]
				editor.DebugExit
				Return
			EndIf		
			f=Max(p,r)				
		Wend
	End Method
	
	Method Initialize(owner:THost)
		host=owner
		SetName name,extensions
		editor=TEditor(host)
		If Not editor RuntimeError "CPP plugin Required EditorHost"
		options=New TCOptions.CreateCOptions(Self)
		editmenu=CreateEditMenu()
		tokens=New TExactQuickHelp
		For Local t$=EachIn keywords
			tokens.AddCommand t,t,""
		Next
	End Method

	Function CreateEditMenu:TGadget()
		Local	edit:TGadget
		Const MKEY$=Chr(8)
		edit=CreateMenu("&Edit",0,Null)
		CreateMenu "&Quick Help"+MKEY+"F1",MENUQUICKHELP,edit
		CreateMenu "",0,edit
		CreateMenu "Cu&t"+MKEY+"Ctrl+X",MENUCUT,edit
		CreateMenu "&Copy"+MKEY+"Ctrl+C",MENUCOPY,edit
		CreateMenu "&Paste"+MKEY+"Ctrl+V",MENUPASTE,edit
		CreateMenu "",0,edit
		CreateMenu "Select &All"+MKEY+"Ctrl+A",MENUSELECTALL,edit
		CreateMenu "",0,edit
'		CreateMenu "Block Indent"+MKEY+"Ctrl+TAB",MENUINDENT,edit
'		CreateMenu "Block Outdent"+MKEY+"Shift TAB",MENUOUTDENT,edit
		CreateMenu "Block Indent"+MKEY+"Ctrl+]",MENUINDENT,edit
		CreateMenu "Block Outdent"+MKEY+"Ctrl+[",MENUOUTDENT,edit
		CreateMenu "",0,edit
		CreateMenu "&Find"+MKEY+"Ctrl+F",MENUFIND,edit
		CreateMenu "Find Next"+MKEY+"F3",MENUFINDNEXT,edit
		CreateMenu "&Replace"+MKEY+"Ctrl+H",MENUREPLACE,edit
		CreateMenu "&Goto Line"+MKEY+"Ctrl+G",MENUGOTO,edit
		Return edit
	End Function

End Type

Type TCCode Extends TOpenCode

	Field	cppplugin:TCPlugin
	Field	options:TCOptions

	Function Open:TCCode(plugin:TCPlugin,path$)
		Local	code:TCCode
		Local	stream:TStream		

		If path
			stream=ReadFile(path)
			If Not stream 
				Notify "Open could not read from "+path
				Return Null
			EndIf
			CloseStream stream
		Else
			Throw "temp cpp files not allowed"
		EndIf

		code=New TCCode
'		code.isbmx=True
		code.cppplugin=plugin
		code.host=plugin.host		
		code.options=plugin.options

		code.active=True
		code.path=path

		plugin.host.addpanel(code)		
		code.textarea=CreateTextArea(0,0,ClientWidth(code.panel),ClientHeight(code.panel),code.panel,0)
		SetGadgetFilter code.textarea,code.FilterKey,code
		SetTextAreaText code.textarea,"~n"
		SetGadgetLayout code.textarea,1,1,1,1
		code.Refresh
		code.SetDirty False
		plugin.host.UpdateProgress "Reading Stream"
		code.ReadSource(path)
		code.UpdateCode
		code.undolist.clear
' cludge first dirty		
		code.filesrc=TextAreaText(code.textarea)
		code.SetDirty False
		Return code
	End Function

	Method OnEvent()
 		Select EventSource()
			Case textarea 
				Select EventID() 
					Case EVENT_GADGETMENU
						PopupWindowMenu host.window,cppplugin.editmenu
					Case EVENT_GADGETACTION
						UpdateCode
					Case EVENT_GADGETSELECT
						UpdateCursor
				End Select
		End Select
	End Method

	Method Refresh()
		Local	rgb:TColorButton
		Local	src$
		SetTextAreaTabs textarea,options.tabsize.value
		SetTextAreaFont textarea,options.editfont.font
		rgb=options.editcolor
		SetTextAreaColor textarea,rgb.red,rgb.green,rgb.blue,True
		rgb=options.styles[0].color
		SetTextAreaColor textarea,rgb.red,rgb.green,rgb.blue,False
		src=cleansrc
		cleansrc=""
		cleansrcl=""
		cursorpos=0
		SetCode(src)
	End Method

'	Method BuildSource(quick,debug,gui,run)		
'		Local cmd$,out$,arg$
'		Throw "simon come here!"
'	End Method

	Method BuildSource(quick,debug,gui,run)		
		Local cmd$,out$,arg$
		Local bmkpath$
		
		bmkpath=getenv_("BMKPATH")
		If Not bmkpath Notify "BlitzMax must be correctly installed to build C++ files";Return
		
		cmd$=quote(bmkpath)
		cmd$:+" makeapp"
		If run cmd$:+" -x"
		If debug cmd$:+" -d" Else cmd$:+" -r"	'-v
		If gui cmd$:+" -t gui"
		If Not quick cmd$:+" -a"
		If debug 
			out$=StripExt(host.FullPath(path))+".debug"
			cmd$:+" -o "+quote(out$)+" "			'QUOTES+out$+QUOTES+" "
		EndIf		
		cmd$:+" "+quote(host.FullPath(path))
		If run
			arg$=host.GetCommandLine()
			If arg cmd$:+" "+arg
		EndIf
		host.execute Self,cmd,"Building "+StripExt(StripDir(path)),"",True	',exe$		
	End Method

	Method Help()
		Local	p,l$,a$
'		If Not cpptokens Return
		p=TextAreaCursor(textarea,TEXTAREA_CHARS)
		a$=WordAtPos(cleansrc,p)
		If a$=helpcmd
			l$=cppplugin.tokens.link(a$)
			If l$ 
				host.Help l$	'bmxpath+l$
			EndIf
		Else
			helpcmd=a$
			helpstring$=cppplugin.tokens.help(a$)
			UpdateStatus	'host.setstatus helpstring$
		EndIf
	End Method

	Method GetNode:TDataNode()
		Local	root:TCodeNode,n:TCodeNode
		Local	src$,line,col
		Local	p,p1,r,t,m,f,l,e
		
		n=New TCodeNode
		n.name=StripDir(path)
		n.owner=Self
		n.count=src.length
		root=n
' fill in code tree here
		If codenode
			codenode.Sync(root)	
			root.Free
		Else
			codenode=root
		EndIf
		Return codenode
	End Method

	Function IsCode(src$,p)
		Local	l,q,n
		l=src.FindLast(EOL$,src.length-p)
		If l=-1 l=0
		q=src.Find(";",l)
		If q<>-1 And q<p Return
		q=l
		While q<p
			q=src.Find(Chr(34),q)+1
			If q=0 Exit
			If q<=p n:+1
		Wend
		If n&1 Return
		Return True		
	End Function

	Function FindLabel(src$,pos)
		Local	p,q,c
		While pos>=0
			p=src.Find("#",pos)
			If p=-1 Exit
			q=p
			While q>0
				q:-1
				c=src[q]
				If c=13 Return p
				If c=10 Return p
				If c=32 Or c=9 Continue
				Exit
			Wend
			If q=0 Return p
			pos=p+1
		Wend
		Return src.length	
	End Function
	
	Function FindToken(token$,src$,pos)	'lowercase src only!
		Local	p,c,n
		n=token.length
		While pos>=0
			p=src.Find(token,pos)
			If p=-1 Exit
			c=10 If p>0 c=src[p-1]
			If isntalphanumeric(c) 
				If p+n<src.length c=src[p+n]
				If isntalphanumeric(c) And iscode(src,p) Return p
			EndIf
			pos=p+1
		Wend
		Return src.length
	End Function

	Function IsFirstCharOnLine(src$,pos)
		Local	i,c
		For i=1 To pos
			c=src[pos-i]
			If c=10 Or c=13 Return True
			If c>32 Return False
		Next
		Return True
	End Function

' block rem detectors /* blah blah */

	Function FindRem(src$,pos)
		Return FindToken("/*",src,pos)
	End Function

	Function FindEndRem(src$,pos,returnlast=False)
		Local t=FindToken("*/",src,pos)+2
		If t>src.length t=src.length
		Return t 
	End Function

	Function FindPrevRem(src$,pos)	'lowercase src only!
		Local	p,c
		While pos>0
			If pos>src.length Exit
			p=src.FindLast("/*",src.length-pos)
			If p=-1 Exit						
			Return p
			pos=p-1
		Wend
		Return -1
	End Function

	Method WasRemmed(pos,src$)
		Local	s$,p
		s$=cleansrcl
		p=src.length-s.length
		If p<0 pos:-p		
		p=FindPrevRem(s$,pos)	'+3
		If p=-1 Return
		p=FindEndRem(s$,p)
		If pos<p Return True
	End Method

	Method SetCode(src$,diff:TDiff=Null)
		Local	i,p,p1,q,r,a,t$,h$,lsrc$,r0,r1,cpos,autocap
		Local	s:TTextStyle

' update dirty flag	
		CheckDirty src
		If src=cleansrc And Not deferpos And Not diff Return	

		If Not options.syntaxhighlight	'Not isbmx Or 
			cleansrc=src
			cleansrcl=src.ToLower()
			Return
		EndIf		
' doit
		autocap=True		'autocapitalize
		lsrc=src.ToLower()
		cpos=TextAreaCursor(textarea,TEXTAREA_CHARS)
		LockTextArea textarea
' calculate highlight region
		If deferpos
			p=deferpos
			p1=p
			deferpos=0
		Else
			If diff
				p=diff.pos
				p1=p+diff.add.length
			Else
				p=firstdiff(src,cleansrc)
				p1=lastdiff(src,cleansrc)
			EndIf
			q=src.length-cleansrc.length
			If p1-p<q p1=p+q
			If p1<p p1=p
		EndIf
' round region to line breaks
		If p>0 p:-1
		If p>src.length p=src.length
		p=src.findlast(EOL,src.length-p)+1
		p1=src.find(EOL,p1)+1
		If p1=0 p1=Len src
' if endrem between p0,p1 and next rem after p1 move p1 forwards
		r1=FindEndRem(lsrc,p)
		If r1<p1 And wasremmed(r1+2,lsrc)	'+6
			r0=FindRem(lsrc,r1+2)	'+6
			If r0>p1 p1=r0
		EndIf
' if rem between p0,p1 and matching endrem after p1 move p1 forewards 
		r0=FindPrevRem(lsrc,p1)
		If r0>=p And r0+3<>cpos	'defer fix
			r1=FindEndRem(lsrc,r0,True)
			If r1>p1 p1=r1
		EndIf
' if rem before p0 and matching endrem after p0 highlight to endrem and move p0 forwards
		r0=FindPrevRem(lsrc,p)
		If r0<>-1 And r0<p
			r1=FindEndRem(lsrc,r0,True)
			If r1>p
				s=options.styles[COMMENT]
				If r1>p s.Format(textarea,p,r1-p)
				p=r1
			EndIf
		EndIf	
' if was remmed and now isn't move p1 down to nearest rem or endrem
		If WasRemmed(p,lsrc)
			r0=FindRem(lsrc,p)
			r1=FindEndRem(lsrc,r0,True)
			p1=Max(p1,Min(r0,r1))	
		EndIf
' highlight code
		s=options.styles[NORMAL]
		If p1>p s.format(textarea,p,p1-p)
		While p<p1
			host.UpdateProgress("Highlighting Code",(p*100)/p1)
			a=src[p]
' quoted strings
			If a=34
				q=p1
				r=src.find(Chr(34),p+1)
				If r>-1 And r<q q=r+1
				r=src.find(EOL,p+1)
				If r>-1 And r<q q=r
				s=options.styles[QUOTED]
				s.format(textarea,p,q-p)
				p=q
				Continue
			EndIf
' single line comments
			If a=47 And src[p+1]=47	'//
				q=p1
				r=src.find(EOL,p+1)
				If r>-1 And r<q q=r
				s=options.styles[COMMENT]
				s.format(textarea,p,q-p)
				p=q
				Continue				
			EndIf
' block comment		
			If a=47 And src[p+1]=42	'/*
				q=p1
				r=FindEndRem(lsrc,p,True)
				If r>-1 And r<q q=r				
				s=options.styles[COMMENT]
				s.format(textarea,p,q-p)
				p=q
				Continue
			EndIf
' tokens
			If (a>=65 And a<91) Or (a>=97 And a<123) Or (a=95) Or (a=Asc("#"))
				q=p+1
				While q<p1
					a=src[q]
					If a<46 Exit
					If a=47 Exit
					If a>=58 And a<65 Exit
					If a>=91 And a<95 Exit
					If a=96 Exit
					If a>122 Exit
					q:+1
				Wend
				t$=src[p..q]
				h$=cppplugin.tokens.token(t$)
				If h$
					If h$<>t$ And autocap
						If q<>cpos
							SetTextAreaText textarea,h$,p,Len h
						Else
							deferpos=q
						EndIf
					EndIf
					s=options.styles[KEYWORD]
Rem					
					If h$="Rem" And IsFirstCharOnLine(lsrc,p)	' Not (p>4 And lsrc[p-4..p]="end ")
						If q<>cpos
							q=FindEndRem(lsrc,p,True)
							s=options.styles[COMMENT]
						Else
							deferpos=q
						EndIf						
					EndIf
EndRem
					s.format(textarea,p,q-p)
				EndIf
				p=q
				Continue
			EndIf
			
			p:+1
		Wend
		UnlockTextArea textarea
		cleansrc=src
		cleansrcl=lsrc
		CheckDirty src
	End Method

End Type
